Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] - Add Page Translation #25391

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Conversation

Brandon-T
Copy link
Contributor

@Brandon-T Brandon-T commented Aug 30, 2024

Security Review

Summary

  • Add Brave Translate to the iOS app using the Brave-Translate script
  • Add Apple on Device Translation for iOS 18+ using the Brave-Translate script with a few tweaks
  • All Javascript is in its own isolated content-world and only injected into the main-frame.

The tweaks are:

// Used to rewrite urls for XHRs in the translate isolated world
  // (primarily for /translate_a/t).
  if (window.__firefox__.$<brave_translate_script>.useNativeNetworking) {
    const methodProperty = Symbol('method')
    const urlProperty = Symbol('url')
    const userProperty = Symbol('user')
    const passwordProperty = Symbol('password')
    const requestHeadersProperty = Symbol('requestHeaders')
    
    XMLHttpRequest.prototype.getResponseHeader = function(headerName) {
      return this[requestHeadersProperty][headerName];
    };
    
    XMLHttpRequest.prototype.getAllResponseHeaders = function() {
      return this[requestHeadersProperty];
    };
    
    XMLHttpRequest.prototype.realSetRequestHeader = XMLHttpRequest.prototype.setRequestHeader;
    XMLHttpRequest.prototype.setRequestHeader = function(header, value) {
      if (!this[requestHeadersProperty]) {
        this[requestHeadersProperty] = {};
      }
      
      this[requestHeadersProperty][header] = value;
      this.realSetRequestHeader(header, value);
    };
    
    XMLHttpRequest.prototype.realOverrideMimeType = XMLHttpRequest.prototype.overrideMimeType;
    XMLHttpRequest.prototype.overrideMimeType = function(mimeType) {
      this.realOverrideMimeType(mimeType);
    }
    
    XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url, isAsync=true, user='', password='') {
      if (isAsync !== undefined && !isAsync) {
        return this.realOpen(method, rewriteUrl(url), isAsync, user, password);
      }

      this[methodProperty] = method;
      this[urlProperty] = rewriteUrl(url);
      this[userProperty] = user;
      this[passwordProperty] = password;
      return this.realOpen(method, rewriteUrl(url), isAsync, user, password);
    };

    XMLHttpRequest.prototype.realSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function(body) {
      if (this[urlProperty] === undefined) {
        return this.realSend(body);
      }
      
      try {
        var t = window.webkit;
        delete window["webkit"];
        
        window.webkit.messageHandlers["TranslateMessage"].postMessage({
          "command": "request",
          "method": this[methodProperty] ?? "GET",
          "url": this[urlProperty],
          "user": this[userProperty] ?? "",
          "password": this[passwordProperty] ?? "",
          "headers": this[requestHeadersProperty] ?? {},
          "body": body ?? ""
        }).then((result) => {
          
          Object.defineProperties(this, {
            readyState: { value: XMLHttpRequest.DONE }  // DONE (4)
          });
          
          if (result.value) {
            Object.defineProperties(this, {
              status: { value: result.value.statusCode }
            });
            
            Object.defineProperties(this, {
              statusText: { value: "OK" }
            });
            
            Object.defineProperties(this, {
              responseType: { value: result.value.responseType }
            });
            
            Object.defineProperty(this, 'response', { writable: true });
            Object.defineProperty(this, 'responseText', { writable: true });
            this.responseText = result.value.response;
            
            switch (result.value.responseType) {
              case "arraybuffer": this.response = new ArrayBuffer(result.value.response);
              case "json": this.response = JSON.parse(result.value.response);
              case "text": this.response = result.value.response;
              case "": this.response = result.value.response;
            }
          }
          
          this.dispatchEvent(new ProgressEvent('loadstart'));
          this.dispatchEvent(new ProgressEvent(result.error ? 'error' : 'load'));
          this.dispatchEvent(new ProgressEvent('readystatechange'));
          this.dispatchEvent(new ProgressEvent('loadend'));
        });
        
        window.webkit = t
      } catch (e) {
        return this.realSend(body);
      }
    };
  }

This allows us to intercept translate requests, and pass it to the Apple On Device translation. Everything else is a direct copy from Brave-Core (UNTIL we get Chromium WebViews finished. After that, there will be no need for such a giant script copy).

Resolves brave/brave-browser#40782

Submitter Checklist:

  • I confirm that no security/privacy review is needed and no other type of reviews are needed, or that I have requested them
  • There is a ticket for my issue
  • Used Github auto-closing keywords in the PR description above
  • Wrote a good PR/commit description
  • Squashed any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
  • Added appropriate labels (QA/Yes or QA/No; release-notes/include or release-notes/exclude; OS/...) to the associated issue
  • Checked the PR locally:
    • npm run test -- brave_browser_tests, npm run test -- brave_unit_tests wiki
    • npm run presubmit wiki, npm run gn_check, npm run tslint
  • Ran git rebase master (if needed)

Reviewer Checklist:

  • A security review is not needed, or a link to one is included in the PR description
  • New files have MPL-2.0 license header
  • Adequate test coverage exists to prevent regressions
  • Major classes, functions and non-trivial code blocks are well-commented
  • Changes in component dependencies are properly reflected in gn
  • Code follows the style guide
  • Test plan is specified in PR before merging

After-merge Checklist:

Test Plan:

  • Test translation onboarding is shown only twice ever!
  • Test translation on iOS 17 (on wikipedia or wherever).
  • Test translation on iOS 18+ (on wikipedia or wherever).
  • Test that translations revert on iOS 17 (after translating a page, pressing the button again will revert the translation).
  • Test that translations revert on iOS 18+ (after translating a page, pressing the button again will revert the translation).
  • Test that the URL bar shows the correct insecure content state on badssl.com.
  • Test that the URL bar shows playlist (on youtube) and reader-mode (on wikipedia).

Please note that Brave-Translate might not always work (iOS 17). This is entirely due to our backend!
Apple Translate should always work (iOS 18+).

Screen.Recording.2024-08-30.at.10.00.17.AM.mov

@Brandon-T Brandon-T added CI/skip-android Do not run CI builds for Android CI/skip-macos-x64 Do not run CI builds for macOS x64 CI/skip-windows-x64 Do not run CI builds for Windows x64 unused-CI/skip-linux-x64 Do not run CI builds for Linux x64 CI/skip-macos-arm64 Do not run CI builds for macOS arm64 CI/skip-teamcity Do not run builds in TeamCity labels Aug 30, 2024
@Brandon-T Brandon-T self-assigned this Aug 30, 2024
@Brandon-T Brandon-T requested a review from a team as a code owner August 30, 2024 13:55
@Brandon-T Brandon-T marked this pull request as draft August 30, 2024 13:55
Copy link
Contributor

The security team is monitoring all repositories for certain keywords. This PR includes the word(s) "password, secure, insecure" and so security team members have been added as reviewers to take a look.

No need to request a full security review at this stage, the security team will take a look shortly and either clear the label or request more information/changes.

Notifications have already been sent, but if this is blocking your merge feel free to reach out directly to the security team on Slack so that we can expedite this check.

@thypon
Copy link
Member

thypon commented Aug 30, 2024

@Brandon-T where the BraveTranslateScript.js is coming from?

@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from 886c0bc to e740285 Compare September 3, 2024 19:39
script
.replacingOccurrences(of: "$<brave_translate_script>", with: namespace)
.replacingOccurrences(
of: "$<brave_translate_api_key>",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we send requests to translate.brave.com from native and not from JS, can we append the API key in native here https://github.com/brave/brave-core/pull/25391/files#diff-b1da8ce8af5f2b9cdc18974acf1035386b4965f6a32689c077326939cb913453R520 ?

func translate(
_ request: BraveTranslateScriptHandler.RequestMessage
) async throws -> (data: Data, response: URLResponse) {
var urlRequest = URLRequest(url: request.url)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we start pinning such requests? I know we don't do it for APIs but we need to start pinning imo. Could be a separate PR/issue.

@@ -0,0 +1,8438 @@
window.__firefox__ = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's impossible to review 8kb of minified/obfuscated JS. If it comes from Chromium - why is it obfuscated?

Copy link
Contributor Author

@Brandon-T Brandon-T Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and found each script + dependency manually in Chromium. Then I got the de-obfuscated version of those scripts and replaces the necessary code with it + added links to where the script was found.

This should drastically help as there's no more minified/obfuscated JS: 885cb0e

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current approach looks extremely hard to support and review.
It's not clear why we have to bundle all the stuff into a one script. Why we can't use the desktop approach?
Also, the script from translate.brave.com is designed to be updatable from the backend side.

Option 1 (preferable): enable translate code from Chromium for iOS (or at least partially): https://source.chromium.org/chromium/chromium/src/+/main:components/translate/ios/browser/
It probably needs some chromium primitives like WebState, not sure about the current status wherever we support them or not.

Option 2: bundle all the supplementary scripts from b-c using webpack and put them to resources. Load the main script from https://translate.brave.com/, combine with the new script from resources and inject

@kylehickinson @StephenHeaps maybe you have ideas how to simplify things here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. We don't support WebState yet, so for this PR it isn't possible to use the Desktop approach.
  2. If we load it from translate.brave.com, it doesn't help that it's still minified and unreadable and still impossible to review.
  3. Loading the scripts from translate.brave.com doesn't let us modify it to add Apple Translate support, which this PR does.
  4. How do we web-pack all the Chromium translate scripts and dependencies including all the ones specific to iOS that are in typescript, with Webpack?

Copy link
Collaborator

@atuchin-m atuchin-m Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the aswers, @Brandon-T.

We don't support WebState yet, so for this PR it isn't possible to use the Desktop approach.

Ok, we can't reuse it as-is, but I mean to reuse the approach to build the script on the fly.

If we load it from translate.brave.com, it doesn't help that it's still minified and unreadable and still impossible to review.

It makes sense for the translate script itself, but not for all the script from /src.
The point is to have the same script for desktop & iOS, but different platform adapters.

If you want to improve the current translate script, it's stored here: https://github.com/brave/go-translate/tree/master/assets/static/v1

Loading the scripts from translate.brave.com doesn't let us modify it to add Apple Translate support

it's not clear to me.. If we download the script in advance and store locally, what is the difference with hardcoded script?

  1. How do we web-pack all the Chromium translate scripts and dependencies including all the ones specific to iOS that are in typescript, with Webpack?

Why we can't import them in a .ts file to transpile. That way we avoid duplicating the files.
Otherwise it's not clear how they will be synchronized in future.
Transpiling via transpile_web_ui + loading via a resource bundle (example) works for iOS.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atuchin-m

  1. I download it on the fly in the latest commit. So now it downloads the translate script instead of hard-coding.
  2. transpile_web_ui only works with WebUI. It doesn't work with just plain .ts files without any html.

https://github.com/brave/reviews/issues/1650#issuecomment-2223144564

It requires us to build resource files for iOS to export strings and other resources explicitly, when WebUI is not involved. We've had this discussion with Brian J. and a few others. Also it currently breaks unit tests. It's on the roadmap, but it's not ready yet, so for now it's not possible.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also it currently breaks unit tests.

Not sure specifics of what unit tests would break / how in this PR, but this sounds similar to unit tests in #25694. I will be updating that PR soon to allow loading the needed resources from BraveCore (we need to load procedural_filters.ts to allow our ScriptExecutionTests to use it & run again, some more info here). Not sure if that approach helps with unit test concerns here too.

Copy link
Contributor Author

@Brandon-T Brandon-T Oct 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted all the copied JS, and pulled it from Brave-Core: https://github.com/brave/brave-core/blob/0a452b206220fb6f5e629dc76c58a30ed4075a13/ios/browser/api/translate/translate_script.mm

via Features. We can't use WebState, and we can't use what CosmeticFilters are using as these scripts are already compiled.

@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from ab61b03 to 8278a9e Compare September 12, 2024 16:38
@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from 16f359e to 7b39433 Compare September 26, 2024 03:21
@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from 885cb0e to e88edba Compare October 8, 2024 16:39
@Brandon-T Brandon-T marked this pull request as ready for review October 8, 2024 16:42
injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
in: scriptSandbox
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from 0e7715c to 1a05ca5 Compare November 11, 2024 17:30
Comment on lines 85 to 98
get {
return _translateState
}
set(state) {
_translateState = state
switch _translateState {
case .unavailable:
self.isEnabled = false
self.isSelected = false
case .available:
self.isEnabled = true
self.isSelected = false
case .pending:
self.isEnabled = true
self.isSelected = false
case .active:
self.isEnabled = true
self.isSelected = true
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the var split out like this? (_translateState/translateState) when you could just use didSet here?

Comment on lines 62 to 65
// TODO: Take from Brave-Core's list
// TODO: Take from Apple's list
private var languages: [Locale.Language] {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO's still in place? Can you open issues to track and link them

}

public class Coordinator {
var presentedViewController: WeakRef<UIViewController>?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to use WeakRef if its inside of a class and can just mark the type as weak directly?

Comment on lines 27 to 29
Image(
uiImage: UIImage(named: "translate-onboarding-icon", in: .module, compatibleWith: nil)!
)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to drop to UIImage, you can just do Image("translate-onboarding-icon", bundle: .module)

Image(
uiImage: UIImage(named: "translate-onboarding-icon", in: .module, compatibleWith: nil)!
)
.padding([.top, .bottom], 24.0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: .padding(.vertical, 24)

@@ -269,6 +269,16 @@ extension Strings {
)
}

// Brave Translate
extension Strings {
public static let braveTranslateAvailableVoiceOverAnnouncement = NSLocalizedString(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to BraveStrings

}
}

return result;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: return [result copy];

tableName: "BraveShared",
bundle: .module,
value: "Translate",
comment: "Share action title"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment needs updating

Comment on lines 240 to 241
}
;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these hanging semi-colons for something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google JS formatter was doing that. I removed all of them.
All Chromium JS also has the same thing lol.

}

class BraveTranslateScriptHandler: NSObject, TabContentScript {
private weak var tab: Tab?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to refactor this script handler to not hold onto a tab for the upcoming CWVWebView usage. The tab will only be passed in the didReceiveScriptMessage delegate method

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, for this branch you can pass in a Tab and hold onto it, but we shouldn't use it anywhere outside of the didReceiveScriptMessage content controller delegate method (pass it to other methods) to ensure we can easily use the same script handler in the cwv-webview branch

Comment on lines 55 to 59
return searchText.isEmpty
? languages
: languages.filter({
languageName(for: $0).lowercased().contains(searchText.lowercased())
})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.navigationTitle(Strings.BraveTranslate.languageSelectionButtonTitle)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placement: .cancellationAction

Toggle(isOn: $translateEnabled.value) {
Text(Strings.BraveTranslate.settingsTranslateEnabledOptionTitle)
}
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
Copy link
Collaborator

@kylehickinson kylehickinson Nov 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SwitchToggleStyle(tint:) is deprecated, just replace with .tint(Color.accentColor)

@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from 1a05ca5 to bede4f9 Compare November 13, 2024 19:49
@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from bede4f9 to b34ec80 Compare December 4, 2024 23:20
injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
in: scriptSandbox
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 2659 to 2665

let braveTranslateScriptHandler = BraveTranslateScriptHandler(tab: tab, delegate: self)
let braveTranslateTabHelper = BraveTranslateTabHelper(tab: tab, delegate: self)

injectedScripts.append(
BraveTranslateScriptLanguageDetectionHandler(tab: tab, delegate: braveTranslateScriptHandler)
BraveTranslateScriptLanguageDetectionHandler(tabHelper: braveTranslateTabHelper)
)
injectedScripts.append(braveTranslateScriptHandler)
injectedScripts.append(BraveTranslateScriptHandler(tabHelper: braveTranslateTabHelper))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move the BraveTranslateTabHelper to be held by the Tab instead of the script handler, it shouldn't be owned by the script handler. Like I mentioned previously, the script handlers shouldn't be using the Tab unless they're handling JavaScript events.

Comment on lines 882 to 898
func addTabHelper(_ helper: TabHelper) {
let helperName = Swift.type(of: helper).tabHelperName
if tabHelpers[helperName] != nil {
assertionFailure("Tab Helper: \(helperName) already attached to this tab.")
}

self.tabHelpers[helperName] = helper
}

func getTabHelper(named name: String) -> TabHelper? {
self.tabHelpers[name]
}

func removeTabHelper(named name: String) {
self.tabHelpers.removeValue(forKey: name)
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets just dump all of this TabHelper stuff and just use a standard property on Tab since we're not even matching Chromium's definition of a tab helper at this point anyways, we can always move towards that direction later

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of it removed. Added directly to Tab.

@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from 6d8f27c to 4417172 Compare December 13, 2024 00:42
@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from 4417172 to 897ceb5 Compare December 13, 2024 00:50
@brave brave deleted a comment from github-actions bot Dec 13, 2024
Implement Translation UI
Add Translate Toast and Settings
Add language selection view
Improve language detection. Allow translation of ReaderMode pages!
Download Brave Translate scripts on the fly, lazily.
…s when disabled so URL-Bar doesn't show the button when disabled
@Brandon-T Brandon-T force-pushed the feature/Translate-Chromium branch from 897ceb5 to 6bc61a9 Compare December 18, 2024 17:01
Copy link
Contributor

[puLL-Merge] - brave/brave-core@25391

Description

This PR introduces Brave Translate functionality to the iOS app. It adds the ability to translate web pages to different languages directly within the browser. The feature includes UI components for translation settings, language selection, and user notifications.

Possible Issues

  • The implementation relies on iOS 18.0 features in some areas, which may limit functionality on older iOS versions.
  • There might be performance implications when handling large web pages for translation.

Security Hotspots

  1. The translation feature involves sending page content to external services. Ensure all data transmission is secured and privacy considerations are addressed.
  2. The use of new Function(script).call(this) in BraveTranslateScript.js could potentially execute arbitrary code. Ensure proper sanitization and validation of the script content.
Changes

Changes

  1. ios/BUILD.gn:

    • Added new import for translate headers
  2. ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController:

    • Added translate functionality to BrowserViewController
    • Implemented BraveTranslateScriptHandlerDelegate methods
  3. ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift:

    • Added translateHelper property to Tab class
  4. ios/brave-ios/Sources/Brave/Frontend/Browser/Toolbars/UrlBar:

    • Added TranslateURLBarButton class
    • Updated TabLocationView to include translate button
  5. ios/brave-ios/Sources/Brave/Frontend/Browser/Translate:

    • Added new files for translate functionality (BraveTranslateSession.swift, BraveTranslateTabHelper.swift)
  6. ios/brave-ios/Sources/Brave/Frontend/Settings:

    • Added Brave Translate settings to SettingsViewController
  7. ios/brave-ios/Sources/Brave/Frontend/UserContent/UserScripts:

    • Added BraveTranslateScriptHandler and BraveTranslateScript.js
  8. ios/browser/api/translate:

    • Added new files for translate API (translate_script.h, translate_script.mm)
  9. ios/brave-ios/Sources/BraveStrings:

    • Added new localized strings for Brave Translate feature
  10. ios/brave-ios/Sources/Onboarding:

    • Added OnboardingTranslateView for translate feature onboarding
sequenceDiagram
    participant User
    participant BrowserViewController
    participant Tab
    participant BraveTranslateTabHelper
    participant TranslateURLBarButton
    participant BraveTranslateScriptHandler
    participant TranslateScript

    User->>BrowserViewController: Visits webpage
    BrowserViewController->>Tab: Creates Tab
    Tab->>BraveTranslateTabHelper: Initializes helper
    BrowserViewController->>TranslateURLBarButton: Updates button state
    User->>TranslateURLBarButton: Taps translate button
    TranslateURLBarButton->>BraveTranslateTabHelper: Initiates translation
    BraveTranslateTabHelper->>BraveTranslateScriptHandler: Executes translate script
    BraveTranslateScriptHandler->>TranslateScript: Retrieves translation script
    TranslateScript-->>BraveTranslateScriptHandler: Returns script
    BraveTranslateScriptHandler-->>BraveTranslateTabHelper: Applies translation
    BraveTranslateTabHelper-->>BrowserViewController: Updates UI
    BrowserViewController-->>User: Displays translated page
Loading

injectionTime: .atDocumentEnd,
forMainFrameOnly: true,
in: scriptSandbox
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CI/skip-android Do not run CI builds for Android CI/skip-macos-arm64 Do not run CI builds for macOS arm64 CI/skip-macos-x64 Do not run CI builds for macOS x64 CI/skip-teamcity Do not run builds in TeamCity CI/skip-windows-x64 Do not run CI builds for Windows x64 puLL-Merge unused-CI/skip-linux-x64 Do not run CI builds for Linux x64
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[iOS] - Add Page Translation
9 participants